package com.myibatis.aop;

import com.myibatis.annotation.Arguments;
import com.myibatis.annotation.ResultType;
import com.myibatis.annotation.Sql;
import com.myibatis.def.SimpleDaoConstants;
import com.myibatis.spring.rowMapper.MiniColumnMapRowMapper;
import com.myibatis.spring.rowMapper.MiniColumnOriginalMapRowMapper;
import com.myibatis.util.DaoUtil;
import com.myibatis.util.FreemarkerParseFactory;
import com.myibatis.util.SimpleDaoPage;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.ColumnMapRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
//import org.springframework.jdbc.core.simple.ParameterizedBeanPropertyRowMapper;

import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import ognl.Ognl;
import ognl.OgnlException;

/**
 * Created with IntelliJ IDEA.
 * User: wangchen
 * Date: 16-5-30
 * Time: 下午1:37
 * To change this template use File | Settings | File Templates.
 */
public class SimpleDaoHandler implements InvocationHandler {
    private static final Logger logger = Logger.getLogger(SimpleDaoHandler.class);
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;


    private String UPPER_KEY = "upper";

    private String LOWER_KEY = "lower";
    /**
     * map的关键字类型 三个值
     */
    private String keyType = "origin";
    private boolean formatSql = false;

    private boolean showSql = false;

    private String dbType;

    public JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public NamedParameterJdbcTemplate getNamedParameterJdbcTemplate() {
        return namedParameterJdbcTemplate;
    }

    public void setNamedParameterJdbcTemplate(NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
        this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
    }

    public String getKeyType() {
        return keyType;
    }

    public void setKeyType(String keyType) {
        this.keyType = keyType;
    }

    public boolean isFormatSql() {
        return formatSql;
    }

    public void setFormatSql(boolean formatSql) {
        this.formatSql = formatSql;
    }

    public boolean isShowSql() {
        return showSql;
    }

    public void setShowSql(boolean showSql) {
        this.showSql = showSql;
    }

    public String getDbType() {
        return dbType;
    }

    public void setDbType(String dbType) {
        this.dbType = dbType;
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {


        // 返回结果
        Object returnObj = null;
        // SQL模板
        String templateSql = null;
        // SQL模板参数
        Map<String, Object> sqlParamsMap = new HashMap<String, Object>();
        // 分页参数
        SimpleDaoPage pageSetting = new SimpleDaoPage();
        // Step.1装载SQL模板，所需参数

        templateSql = installDaoMetaData(pageSetting, method, sqlParamsMap, args);
        // Step.3解析SQL模板，返回可执行SQL
        String executeSql = parseSqlTemplate(method, templateSql, sqlParamsMap);
        // Step.4 组装SQL占位符参数
        Map<String, Object> sqlMap = installPlaceholderSqlParam(executeSql, sqlParamsMap);
        // Step.5 获取SQL执行返回值
        try {
            returnObj = getReturnMinidaoResult(dbType, pageSetting, method, executeSql, sqlMap);
        } catch (EmptyResultDataAccessException e) {
            returnObj = null;
        }
        logger.debug("returnObj==="+returnObj);
        if (showSql) {
//            logger.info("MiniDao-SQL:\n\n" + (formatSql == true ? formatter.format(executeSql) : executeSql) + "\n");
            logger.info("MiniDao-SQL:\n\n" + executeSql + "\n");
        }
        return returnObj;  //To change body of implemented methods use File | Settings | File Templates.
    }

    /**
     * 装载SQL模板参数
     *
     * @param pageSetting
     *
     * @param method
     * @param sqlParamsMap
     *            返回(装载模板参数)
     * @param args
     * @return templateSql(@SQL标签的SQL)
     * @throws Exception
     */
    private String installDaoMetaData(SimpleDaoPage pageSetting, Method method, Map<String, Object> sqlParamsMap, Object[] args) throws Exception {
        String templateSql = null;
        // 如果方法参数大于1个的话，方法必须使用注释标签Arguments
        boolean arguments_flag = method.isAnnotationPresent(Arguments.class);
        if (arguments_flag) {
            // [1].获取方法的参数标签
            Arguments arguments = method.getAnnotation(Arguments.class);
            logger.debug("@Arguments------------------------------------------" + Arrays.toString(arguments.value()));
            if (arguments.value().length > args.length) {
                // 校验机制-如果注释标签参数数目大于方法的参数，则抛出异常
                throw new Exception("[注释标签]参数数目，不能大于[方法参数]参数数目");
            }
            // step.2.将args转换成键值对，封装成Map对象
            int args_num = 0;
            for (String v : arguments.value()) {
                // update-begin--Author:fancq Date:20140102 for：支持多数据分页
                if (v.equalsIgnoreCase("page")) {
                    pageSetting.setPage(Integer.parseInt(args[args_num].toString()));
                }
                if (v.equalsIgnoreCase("rows")) {
                    pageSetting.setRows(Integer.parseInt(args[args_num].toString()));
                }
                // update-end--Author:fancq Date:20140102 for：支持多数据分页
                sqlParamsMap.put(v, args[args_num]);
                args_num++;
            }
        } else {
            // 如果未使用[参数标签]
            if (args != null && args.length > 1) {
                throw new Exception("方法参数数目>=2，方法必须使用注释标签@Arguments");
            } else if (args != null && args.length == 1) {
                // step.2.将args转换成键值对，封装成Map对象
                //sqlParamsMap.put(MiniDaoConstants.SQL_FTL_DTO, args[0]);
            }

        }

        // [2].获取方法的SQL标签
        if (method.isAnnotationPresent(Sql.class)) {
            Sql sql = method.getAnnotation(Sql.class);
            // 如果用户采用自定义标签SQL，则SQL文件无效
            if (StringUtils.isNotEmpty(sql.value())) {
                templateSql = sql.value();
            }
            logger.debug("@Sql------------------------------------------" + sql.value());
        }
        logger.debug("sqlParamsMap------------------------------------------" + sqlParamsMap);
        return templateSql;
    }



    /**
     * 解析SQL模板
     *
     * @param method
     * @param templateSql
     * @param sqlParamsMap
     * @return 可执行SQL
     */
    private String parseSqlTemplate(Method method, String templateSql, Map<String, Object> sqlParamsMap) {
        // step.1.根据命名规范[接口名_方法名.sql]，获取SQL模板文件的路径
        String executeSql = null;

        // step.2.获取SQL模板内容
        // step.3.通过模板引擎给SQL模板装载参数,解析生成可执行SQL
        if (StringUtils.isNotEmpty(templateSql)) {
            executeSql = FreemarkerParseFactory.parseTemplateContent(templateSql, sqlParamsMap);
        } else {
            String sqlTempletPath = method.getDeclaringClass().getName().replace(".", "/").replace("/dao/", "/sql/") + "_" + method.getName() + ".sql";
            if (!FreemarkerParseFactory.isExistTemplate(sqlTempletPath)) {
                sqlTempletPath = method.getDeclaringClass().getName().replace(".", "/") + "_" + method.getName() + ".sql";
            }
            logger.debug("MiniDao-SQL-Path:" + sqlTempletPath);
            executeSql = FreemarkerParseFactory.parseTemplate(sqlTempletPath, sqlParamsMap);
        }
        logger.debug("executeSql:" + executeSql);
        return executeSql;
    }


    /**
     * 组装占位符参数 -> Map
     *
     * @param executeSql
     * @return
     * @throws OgnlException
     */
    private Map<String, Object> installPlaceholderSqlParam(String executeSql, Map sqlParamsMap) throws OgnlException {
        Map<String, Object> map = new HashMap<String, Object>();
        String regEx = ":[ tnx0Bfr]*[0-9a-z.A-Z]+"; // 表示以：开头，[0-9或者.或者A-Z大小都写]的任意字符，超过一个
        Pattern pat = Pattern.compile(regEx);
        Matcher m = pat.matcher(executeSql);
        while (m.find()) {
            logger.debug(" Match [" + m.group() + "] at positions " + m.start() + "-" + (m.end() - 1));
            String ognl_key = m.group().replace(":", "").trim();
            map.put(ognl_key, Ognl.getValue(ognl_key, sqlParamsMap));
        }
        logger.debug("map:::"+map);
        return map;
    }


    /**
     * 获取MiniDao处理结果集
     *
     * @param dbType
     * @param pageSetting
     * @param paramMap
     * @param method
     * @param executeSql
     * @return 结果集
     */
    @SuppressWarnings("unchecked")
    private Object getReturnMinidaoResult(String dbType, SimpleDaoPage pageSetting, Method method, String executeSql, Map<String, Object> paramMap) {
        // step.4.调用SpringJdbc引擎，执行SQL返回值
        // 5.1获取返回值类型[Map/Object/List<Object>/List<Map>/基本类型]
        String methodName = method.getName();
        // 判斷是否非查詢方法
        if (checkActiveKey(methodName)) {
            if (paramMap != null) {
                return namedParameterJdbcTemplate.update(executeSql, paramMap);
            } else {
                return jdbcTemplate.update(executeSql);
            }
        } else if (checkBatchKey(methodName)) {
            return batchUpdate(executeSql);
        } else {
            // 如果是查詢操作
            Class<?> returnType = method.getReturnType();
            if (returnType.isPrimitive()) {
                Number number = jdbcTemplate.queryForObject(executeSql, BigDecimal.class);
                // update-begin--Author:JueYue Date:20140611 for：修复int类型的bug
                if ("int".equals(returnType.getCanonicalName())) {
                    return number.intValue();
                } else if ("long".equals(returnType.getCanonicalName())) {
                    return number.longValue();
                } else if ("double".equals(returnType.getCanonicalName())) {
                    return number.doubleValue();
                }
            } else if (returnType.isAssignableFrom(List.class) || returnType.isAssignableFrom(SimpleDaoPage.class)) {
                int page = pageSetting.getPage();
                int rows = pageSetting.getRows();
                if (page != 0 && rows != 0) {
                    if (returnType.isAssignableFrom(SimpleDaoPage.class)) {
                        if (paramMap != null) {
                            pageSetting.setTotal(namedParameterJdbcTemplate.queryForObject(getCountSql(executeSql), paramMap, Integer.class));
                        } else {
                            pageSetting.setTotal(jdbcTemplate.queryForObject(getCountSql(executeSql), Integer.class));
                        }
                    }
                    executeSql = DaoUtil.createPageSql(dbType, executeSql, page, rows);
                }

                RowMapper resultType = getListRealType(method);
                List list;
                if (paramMap != null) {
                    list = namedParameterJdbcTemplate.query(executeSql, paramMap, resultType);
                } else {
                    list = jdbcTemplate.query(executeSql, resultType);
                }
                if (returnType.isAssignableFrom(SimpleDaoPage.class)) {
                    pageSetting.setResults(list);
                    return pageSetting;
                } else {
                    return list;
                }
            } else if (returnType.isAssignableFrom(Map.class)) {
                // Map类型
                if (paramMap != null) {
                    return namedParameterJdbcTemplate.queryForObject(executeSql, paramMap, getColumnMapRowMapper());
                } else {
                    return jdbcTemplate.queryForObject(executeSql, getColumnMapRowMapper());
                }
            } else if (returnType.isAssignableFrom(String.class)) {
                if (paramMap != null) {
                    return namedParameterJdbcTemplate.queryForObject(executeSql, paramMap, String.class);
                } else {
                    return jdbcTemplate.queryForObject(executeSql, String.class);
                }
            } else if (DaoUtil.isWrapClass(returnType)) {
                if (paramMap != null) {
                    return namedParameterJdbcTemplate.queryForObject(executeSql, paramMap, returnType);
                } else {
                    return jdbcTemplate.queryForObject(executeSql, returnType);
                }
            } else {
                // 对象类型
                //RowMapper<?> rm = ParameterizedBeanPropertyRowMapper.newInstance(returnType);
                RowMapper<?> rm = BeanPropertyRowMapper.newInstance(returnType);
                if (paramMap != null) {
                    return namedParameterJdbcTemplate.queryForObject(executeSql, paramMap, rm);
                    //return namedParameterJdbcTemplate.queryForObject(executeSql, paramMap, returnType);
                } else {
                    return jdbcTemplate.queryForObject(executeSql, rm);
                }
            }
        }
        return null;
    }

    /**
     * 判斷是否是執行的方法（非查詢）
     *
     * @param methodName
     * @return
     */
    private static boolean checkActiveKey(String methodName) {
        String keys[] = SimpleDaoConstants.INF_METHOD_ACTIVE.split(",");
        for (String s : keys) {
            if (methodName.startsWith(s))
                return true;
        }
        return false;
    }

    /**
     * 判斷是否批處理
     *
     * @param methodName
     * @return
     */
    private static boolean checkBatchKey(String methodName) {
        String keys[] = SimpleDaoConstants.INF_METHOD_BATCH.split(",");
        for (String s : keys) {
            if (methodName.startsWith(s))
                return true;
        }
        return false;
    }

    /**
     * 把批量处理的结果拼接起来
     *
     * @Author JueYue
     * @date 2013-11-17
     */
    private void addResulArray(int[] result, int index, int[] arr) {
        int length = arr.length;
        for (int i = 0; i < length; i++) {
            result[index - length + i] = arr[i];
        }
    }

    /**
     * 批处理
     *
     * @Author JueYue
     * @date 2013-11-17
     * @return
     */
    private int[] batchUpdate(String executeSql) {
        String[] sqls = executeSql.split(";");
        if (sqls.length < 100) {
            return jdbcTemplate.batchUpdate(sqls);
        }
        int[] result = new int[sqls.length];
        List<String> sqlList = new ArrayList<String>();
        for (int i = 0; i < sqls.length; i++) {
            sqlList.add(sqls[i]);
            if (i % 100 == 0) {
                addResulArray(result, i + 1, jdbcTemplate.batchUpdate(sqlList.toArray(new String[0])));
                sqlList.clear();
            }
        }
        addResulArray(result, sqls.length, jdbcTemplate.batchUpdate(sqlList.toArray(new String[0])));
        return result;
    }

    /**
     * 获取总数sql - 如果要支持其他数据库，修改这里就可以
     *
     * @param sql
     * @return
     */
    private String getCountSql(String sql) {
        return "select count(0) from (" + sql + ") tmp_count";
    }

    /**
     * 获取真正的类型
     *
     * @param method
     * @return
     */
    private RowMapper<?> getListRealType(Method method) {
        ResultType resultType = method.getAnnotation(ResultType.class);
        if (resultType != null) {
            if (resultType.value().equals(Map.class)) {
                return getColumnMapRowMapper();
            }
//            return ParameterizedBeanPropertyRowMapper.newInstance(resultType.value());
            return BeanPropertyRowMapper.newInstance(resultType.value());
        }
        String genericReturnType = method.getGenericReturnType().toString();
        String realType = genericReturnType.replace("java.util.List", "").replace("<", "").replace(">", "");
        if (realType.contains("java.util.Map")) {
            return getColumnMapRowMapper();
        } else if (realType.length() > 0) {
            try {
//                return ParameterizedBeanPropertyRowMapper.newInstance(Class.forName(realType));
                return BeanPropertyRowMapper.newInstance(Class.forName(realType));
            } catch (ClassNotFoundException e) {
                logger.error(e.getMessage(), e.fillInStackTrace());
                throw new RuntimeException("minidao get class error ,class name is:" + realType);
            }
        }
        return getColumnMapRowMapper();
    }

    /**
     * 根据参数设置map的key大小写
     **/
    private RowMapper<Map<String, Object>> getColumnMapRowMapper() {
        if (getKeyType().equalsIgnoreCase(LOWER_KEY)) {
            return new MiniColumnMapRowMapper();
        } else if (getKeyType().equalsIgnoreCase(UPPER_KEY)) {
            return new ColumnMapRowMapper();
        } else {
            return new MiniColumnOriginalMapRowMapper();
        }
    }
}


